/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cg_testUtils.cpp - testing utilities used mainly for development
//


#include "cg_local.h"


/*
 ==============================================================================

 MODEL TESTING

 ==============================================================================
*/


/*
 ==================
 
 TODO: we need to check the right format extension, also we need to do a trace later
 ==================
*/
void CG_TestModel_f (){

	char	name[MAX_PATH_LENGTH], extension[MAX_PATH_LENGTH >> 4];
	glqVec3	start, end;
	glqVec3	origin;

	if (glqCmdSystem->Argc() > 2){
		G_Printf("Usage: testModel [name]\n");
		return;
	}

	if (cgs.demoPlaying || cgs.loading || !cg.snapshot){
		G_Printf("You must be in a level to test a model\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to test a model\n");
		return;
	}

	// Clear the old model, if any
	CG_ClearTestModel();

	if (glqCmdSystem->Argc() != 2)
		return;

	// FIXME: we support more models than .mdl!

	Str_Copy(name, glqCmdSystem->Argv(1), sizeof(name));
	Str_DefaultFileExtension(name, sizeof(name), ".mdl");

	Str_ExtractFileExtension(name, extension, sizeof(extension));

	// Set up the origin
	origin = cg.renderView.origin + cg.renderView.axis[0] * 100.0f;

	if (cg_testModelSpawn->integerValue){
		if (cg_testModelSpawn->integerValue == 1){
			start = origin;
			end = origin - glqVec3(0.0f, 0.0f, 1000.0f);
		}
		else {
			start = cg.renderView.origin;
			end = cg.renderView.origin + cg.renderView.axis[0] * 1000.0f;
		}

		// TODO: trace here
	}

	// Test the specified model
	cg.testModel.active = true;

	cg.testModel.isGun = false;

	// Set up the render entity
	cg.testModel.renderEntity.type = RE_MODEL;
	cg.testModel.renderEntity.entityNum = ENTITYNUM_NONE;

	cg.testModel.renderEntity.origin = origin;
	cg.testModel.renderEntity.axis.Identity();

	cg.testModel.renderEntity.model = glqRenderer->RegisterModel(name);

	cg.testModel.renderEntity.skin = NULL;

	cg.testModel.renderEntity.hasSubview = false;
	cg.testModel.renderEntity.subviewOrigin = cg.renderView.origin;
	cg.testModel.renderEntity.subviewAngles = cg.renderViewAngles;
	cg.testModel.renderEntity.subviewFovX = 90.0f;
	cg.testModel.renderEntity.subviewFovY = 90.0f;

	cg.testModel.renderEntity.depthHack = false;

	cg.testModel.renderEntity.allowInView = VIEW_ALL;
	cg.testModel.renderEntity.allowShadowInView = VIEW_ALL;

	cg.testModel.renderEntity.material = NULL;

	cg.testModel.renderEntity.materialParms[MATERIALPARM_RED] = 1.0f;
	cg.testModel.renderEntity.materialParms[MATERIALPARM_GREEN] = 1.0f;
	cg.testModel.renderEntity.materialParms[MATERIALPARM_BLUE] = 1.0f;
	cg.testModel.renderEntity.materialParms[MATERIALPARM_ALPHA] = 1.0f;
	cg.testModel.renderEntity.materialParms[MATERIALPARM_TIMEOFFSET] = -MS2SEC(cg.time);
	cg.testModel.renderEntity.materialParms[MATERIALPARM_DIVERSITY] = game.random.RandFloat();
	cg.testModel.renderEntity.materialParms[MATERIALPARM_MISC] = 0.0f;
	cg.testModel.renderEntity.materialParms[MATERIALPARM_MODE] = 0.0f;

	cg.testModel.renderEntity.soundEmitterHandle = 0;

	cg.testModel.time = cg.time;
	cg.testModel.frames = glqRenderer->ModelFrames(cg.testModel.renderEntity.model);
}

/*
 ==================
 CG_TestGun_f
 ==================
*/
void CG_TestGun_f (){

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}

	// Toggle gun testing
	if (cg.testModel.isGun){
		cg.testModel.isGun = false;

		cg.testModel.renderEntity.origin = cg.renderView.origin + cg.renderView.axis[0] * 100.0f;
		cg.testModel.renderEntity.axis.Identity();

		cg.testModel.renderEntity.allowInView = VIEW_ALL;
		cg.testModel.renderEntity.allowShadowInView = VIEW_ALL;

		cg.testModel.renderEntity.depthHack = false;
	}
	else {
		cg.testModel.isGun = true;

		cg.testModel.renderEntity.origin = cg.renderView.origin;
		cg.testModel.renderEntity.axis = cg.renderView.axis;

		cg.testModel.renderEntity.allowInView = VIEW_MAIN;
		cg.testModel.renderEntity.allowShadowInView = VIEW_NONE;

		cg.testModel.renderEntity.depthHack = true;
	}
}

/*
 ==================
 CG_TestMaterial_f
 ==================
*/
void CG_TestMaterial_f (){

	if (glqCmdSystem->Argc() > 2){
		G_Printf("Usage: testMaterial [name]\n");
		return;
	}

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}

	// Clear the old material, if any
	cg.testModel.renderEntity.material = NULL;

	if (glqCmdSystem->Argc() != 2)
		return;

	// Load the material
	cg.testModel.renderEntity.material = glqRenderer->RegisterMaterial(glqCmdSystem->Argv(1));
}

/*
 ==================
 CG_TestMaterialParm_f
 ==================
*/
void CG_TestMaterialParm_f (){

	int		index;

	if (glqCmdSystem->Argc() != 3){
		G_Printf("Usage: testMaterialParm <index> <value | \"time\">\n");
		return;
	}

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}

	// Set the specified material parm to the specified value
	index = Str_ToInteger(glqCmdSystem->Argv(1));
	if (index < 0 || index >= MAX_MATERIAL_PARMS){
		G_Printf("Specified index is out of range\n");
		return;
	}

	if (!Str_ICompare(glqCmdSystem->Argv(2), "time"))
		cg.testModel.renderEntity.materialParms[index] = -MS2SEC(cg.time);
	else
		cg.testModel.renderEntity.materialParms[index] = Str_ToFloat(glqCmdSystem->Argv(2));
}

/*
 ==================
 CG_TestRemoteView_f
 ==================
*/
void CG_TestRemoteView_f (){

	if (glqCmdSystem->Argc() != 4 && glqCmdSystem->Argc() != 7 && glqCmdSystem->Argc() != 9){
		G_Printf("Usage: testRemoteView <origin x y z> [angles x y z] [fov x y]\n");
		return;
	}

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}

	// Set up the subview with the specified values
	cg.testModel.renderEntity.hasSubview = true;

	cg.testModel.renderEntity.subviewOrigin[0] = Str_ToFloat(glqCmdSystem->Argv(1));
	cg.testModel.renderEntity.subviewOrigin[1] = Str_ToFloat(glqCmdSystem->Argv(2));
	cg.testModel.renderEntity.subviewOrigin[2] = Str_ToFloat(glqCmdSystem->Argv(3));

	if (glqCmdSystem->Argc() < 5)
		cg.testModel.renderEntity.subviewAngles.Clear();
	else {
		cg.testModel.renderEntity.subviewAngles[0] = Str_ToFloat(glqCmdSystem->Argv(4));
		cg.testModel.renderEntity.subviewAngles[1] = Str_ToFloat(glqCmdSystem->Argv(5));
		cg.testModel.renderEntity.subviewAngles[2] = Str_ToFloat(glqCmdSystem->Argv(6));

		cg.testModel.renderEntity.subviewAngles.Normalize360();
	}

	if (glqCmdSystem->Argc() < 8){
		cg.testModel.renderEntity.subviewFovX = 90.0f;
		cg.testModel.renderEntity.subviewFovY = 90.0f;
	}
	else {
		cg.testModel.renderEntity.subviewFovX = glqMath::ClampFloat(Str_ToFloat(glqCmdSystem->Argv(7)), 1.0f, 160.0f);
		cg.testModel.renderEntity.subviewFovY = glqMath::ClampFloat(Str_ToFloat(glqCmdSystem->Argv(8)), 1.0f, 160.0f);
	}
}

/*
 ==================
 
 ==================
*/
void CG_FirstFrame_f (){

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}
}

/*
 ==================
 
 ==================
*/
void CG_LastFrame_f (){

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}
}

/*
 ==================
 CG_PrevFrame_f
 ==================
*/
void CG_PrevFrame_f (){

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}

	cg.testModel.renderEntity.frame--;
	if (cg.testModel.renderEntity.frame < 0)
		cg.testModel.renderEntity.frame = cg.testModel.frames - 1;

	cg.testModel.renderEntity.oldFrame = cg.testModel.renderEntity.frame;

	G_Printf("Frame %i\n", cg.testModel.renderEntity.frame);
}

/*
 ==================
 CG_NextFrame_f
 ==================
*/
void CG_NextFrame_f (){

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}

	cg.testModel.renderEntity.frame++;
	if (cg.testModel.renderEntity.frame >= cg.testModel.frames)
		cg.testModel.renderEntity.frame = 0;

	cg.testModel.renderEntity.oldFrame = cg.testModel.renderEntity.frame;

	G_Printf("Frame %i\n", cg.testModel.renderEntity.frame);
}

/*
 ==================
 
 ==================
*/
void CG_JumpToFrame_f (){

	if (glqCmdSystem->Argc() != 1 && glqCmdSystem->Argc() != 2){
		G_Printf("Usage: jumpToFrame [number]\n");
		return;
	}

	if (!cg.testModel.active){
		G_Printf("No active testModel\n");
		return;
	}
}

/*
 ==================
 
 ==================
*/
void CG_UpdateTestModel (){

	glqAngles	angles;

	if (!cg.testModel.active)
		return;

	// Update origin and axis
	if (cg.testModel.isGun){
		cg.testModel.renderEntity.origin = cg.renderView.origin;
		cg.testModel.renderEntity.axis = cg.renderView.axis;

		cg.testModel.renderEntity.origin += cg.renderView.axis[0] * cg_testGunX->floatValue;
		cg.testModel.renderEntity.origin += cg.renderView.axis[1] * cg_testGunY->floatValue;
		cg.testModel.renderEntity.origin += cg.renderView.axis[2] * cg_testGunZ->floatValue;
	}
	else {
		angles.Set(cg_testModelPitch->floatValue, cg_testModelYaw->floatValue, cg_testModelRoll->floatValue);

		if (cg_testModelRotatePitch->floatValue)
			angles.pitch = cg_testModelRotatePitch->floatValue * MS2SEC(cg.time);
		if (cg_testModelRotateYaw->floatValue)
			angles.yaw = cg_testModelRotateYaw->floatValue * MS2SEC(cg.time);
		if (cg_testModelRotateRoll->floatValue)
			angles.roll = cg_testModelRotateRoll->floatValue * MS2SEC(cg.time);

		angles.Normalize360();

		cg.testModel.renderEntity.axis = angles.ToMat3();
	}

	// Update animation
	if (cg_testModelAnimate->integerValue){
		if (cg.time - cg.testModel.time >= 100){
			cg.testModel.time = cg.time;

			cg.testModel.renderEntity.oldFrame = cg.testModel.renderEntity.frame;

			cg.testModel.renderEntity.frame++;
			if (cg.testModel.renderEntity.frame >= cg.testModel.frames)
				cg.testModel.renderEntity.frame = 0;
		}

		cg.testModel.renderEntity.backLerp = 1.0f - (cg.time - cg.testModel.time) * 0.01f;
	}
	else {
		cg.testModel.time = cg.time;

		cg.testModel.renderEntity.backLerp = 0.0f;
	}

	// TODO: bounds

	// If also testing a sound, attach the sound emitter handle to the render
	// entity to allow testing effects modified by sound amplitude
	if (!cg.testSound.active)
		cg.testModel.renderEntity.soundEmitterHandle = 0;
	else
		cg.testModel.renderEntity.soundEmitterHandle = cg.testSound.emitterHandle;

	// Add or update the render entity
	if (!cg.testModel.entityHandle)
		cg.testModel.entityHandle = glqRenderer->AddRenderEntity(cgs.scene, &cg.testModel.renderEntity);
	else
		glqRenderer->UpdateRenderEntity(cgs.scene, cg.testModel.entityHandle, &cg.testModel.renderEntity);
}

/*
 ==================
 CG_ClearTestModel
 ==================
*/
void CG_ClearTestModel (){

	// Free the render entity
	if (cg.testModel.entityHandle)
		glqRenderer->RemoveRenderEntity(cgs.scene, cg.testModel.entityHandle);

	// Clear the test model
	glqMemory->Fill(&cg.testModel, 0, sizeof(testModel_t));
}


/*
 ==============================================================================

 SOUND TESTING

 ==============================================================================
*/


/*
 ==================
 CG_TestSound_f
 ==================
*/
void CG_TestSound_f (){

	if (glqCmdSystem->Argc() != 1 && glqCmdSystem->Argc() != 2 && glqCmdSystem->Argc() != 5){
		G_Printf("Usage: testSound [soundShader] [direction x y z]\n");
		return;
	}

	if (cgs.demoPlaying || cgs.loading || !cg.snapshot){
		G_Printf("You must be in a level to test a sound\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to test a sound\n");
		return;
	}

	// Clear the old sound, if any
	CG_ClearTestSound();

	if (glqCmdSystem->Argc() < 2)
		return;

	// Test the specified sound
	cg.testSound.active = true;

	cg.testSound.played = false;

	// Load the sound shader
	cg.testSound.soundShader = glqSound->RegisterSoundShader(glqCmdSystem->Argv(1));

	// Set up the sound emitter
	cg.testSound.soundEmitter.emitterId = ENTITYNUM_NONE;

	cg.testSound.soundEmitter.origin = cg.renderView.origin + cg.renderView.axis[0] * 100.0f;

	if (glqCmdSystem->Argc() == 2)
		cg.testSound.soundEmitter.direction.Clear();
	else {
		cg.testSound.soundEmitter.direction[0] = Str_ToFloat(glqCmdSystem->Argv(2));
		cg.testSound.soundEmitter.direction[1] = Str_ToFloat(glqCmdSystem->Argv(3));
		cg.testSound.soundEmitter.direction[2] = Str_ToFloat(glqCmdSystem->Argv(4));

		cg.testSound.soundEmitter.direction.Normalize();
	}

	cg.testSound.soundEmitter.underwater = false;

	cg.testSound.soundEmitter.velocity.Clear();

	cg.testSound.soundEmitter.soundParms[SOUNDPARM_VOLUME] = 1.0f;
	cg.testSound.soundEmitter.soundParms[SOUNDPARM_PITCH] = 1.0f;
	cg.testSound.soundEmitter.soundParms[SOUNDPARM_DRYFILTER] = 1.0f;
	cg.testSound.soundEmitter.soundParms[SOUNDPARM_DRYFILTERHF] = 1.0f;
	cg.testSound.soundEmitter.soundParms[SOUNDPARM_WETFILTER] = 1.0f;
	cg.testSound.soundEmitter.soundParms[SOUNDPARM_WETFILTERHF] = 1.0f;
	cg.testSound.soundEmitter.soundParms[SOUNDPARM_MINDISTANCE] = 0.0f;
	cg.testSound.soundEmitter.soundParms[SOUNDPARM_MAXDISTANCE] = 0.0f;
}

/*
 ==================
 CG_TestSoundParm_f
 ==================
*/
void CG_TestSoundParm_f (){

	int		index;

	if (glqCmdSystem->Argc() != 3){
		G_Printf("Usage: testSoundParm <index> <value>\n");
		return;
	}

	if (!cg.testSound.active){
		G_Printf("No active testSound\n");
		return;
	}

	// Set the specified sound parm to the specified value
	index = Str_ToInteger(glqCmdSystem->Argv(1));
	if (index < 0 || index >= MAX_SOUND_PARMS){
		G_Printf("Specified index is out of range\n");
		return;
	}

	cg.testSound.soundEmitter.soundParms[index] = Str_ToFloat(glqCmdSystem->Argv(2));
}

/*
 ==================
 CG_UpdateTestSound
 ==================
*/
void CG_UpdateTestSound (){

	if (!cg.testSound.active)
		return;

	// Set up the sound emitter
	if (CG_Contents(cg.testSound.soundEmitter.origin, bounds_zero, MASK_LIQUID, true, ENTITYNUM_NONE) & MASK_LIQUID)
		cg.testSound.soundEmitter.underwater = true;
	else
		cg.testSound.soundEmitter.underwater = false;

	// Add or update the sound emitter
	if (!cg.testSound.emitterHandle)
		cg.testSound.emitterHandle = glqSound->AddSoundEmitter(&cg.testSound.soundEmitter);
	else
		glqSound->UpdateSoundEmitter(cg.testSound.emitterHandle, &cg.testSound.soundEmitter);

	if (!cg.testSound.emitterHandle)
		return;

	// If currently playing, do nothing
	if (glqSound->PlayingFromEmitter(cg.testSound.emitterHandle, SOUND_CHANNEL_ANY))
		return;

	// If done playing, remove it
	if (cg.testSound.played){
		CG_ClearTestSound();
		return;
	}

	// Play the sound
	cg.testSound.played = true;

	glqSound->PlaySoundFromEmitter(cg.testSound.emitterHandle, SOUND_CHANNEL_ANY, cg.time, cg.testSound.soundShader);
}

/*
 ==================
 CG_ClearTestSound
 ==================
*/
void CG_ClearTestSound (){

	// Remove the sound emitter
	if (cg.testSound.emitterHandle)
		glqSound->RemoveSoundEmitter(cg.testSound.emitterHandle, true);

	// Clear the test sound
	glqMemory->Fill(&cg.testSound, 0, sizeof(testSound_t));
}


/*
 ==============================================================================

 POST-PROCESS MATERIAL TESTING

 ==============================================================================
*/


/*
 ==================
 CG_TestPostProcess_f
 ==================
*/
void CG_TestPostProcess_f (){

	if (glqCmdSystem->Argc() > 2){
		G_Printf("Usage: testPostProcess [material]\n");
		return;
	}

	if (cgs.demoPlaying || cgs.loading || !cg.snapshot){
		G_Printf("You must be in a level to test a post-process material\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to test a post-process material\n");
		return;
	}

	// Clear the old post-process material, if any
	CG_ClearTestPostProcess();

	if (glqCmdSystem->Argc() != 2)
		return;

	// Test the specified post-process material
	cg.testPostProcess.active = true;

	// Set up the post-process material
	cg.testPostProcess.material = glqRenderer->RegisterMaterialNoMip(glqCmdSystem->Argv(1));

	cg.testPostProcess.materialParms[MATERIALPARM_RED] = 1.0f;
	cg.testPostProcess.materialParms[MATERIALPARM_GREEN] = 1.0f;
	cg.testPostProcess.materialParms[MATERIALPARM_BLUE] = 1.0f;
	cg.testPostProcess.materialParms[MATERIALPARM_ALPHA] = 1.0f;
	cg.testPostProcess.materialParms[MATERIALPARM_TIMEOFFSET] = -MS2SEC(cg.time);
	cg.testPostProcess.materialParms[MATERIALPARM_DIVERSITY] = game.random.RandFloat();
	cg.testPostProcess.materialParms[MATERIALPARM_MISC] = 0.0f;
	cg.testPostProcess.materialParms[MATERIALPARM_MODE] = 0.0f;
}

/*
 ==================
 CG_TestPostProcessParm_f
 ==================
*/
void CG_TestPostProcessParm_f (){

	int		index;

	if (glqCmdSystem->Argc() != 3){
		G_Printf("Usage: testPostProcessParm <index> <value | \"time\">\n");
		return;
	}

	if (!cg.testPostProcess.active){
		G_Printf("No active testPostProcess\n");
		return;
	}

	// Set the specified material parm to the specified value
	index = Str_ToInteger(glqCmdSystem->Argv(1));
	if (index < 0 || index >= MAX_MATERIAL_PARMS){
		G_Printf("Specified index is out of range\n");
		return;
	}

	if (!Str_ICompare(glqCmdSystem->Argv(2), "time"))
		cg.testPostProcess.materialParms[index] = -MS2SEC(cg.time);
	else
		cg.testPostProcess.materialParms[index] = Str_ToFloat(glqCmdSystem->Argv(2));
}

/*
 ==================
 CG_UpdateTestPostProcess
 ==================
*/
void CG_UpdateTestPostProcess (){

	if (!cg.testPostProcess.active)
		return;

	// Draw the material
	glqRenderer->SetColor1(1.0f);
	glqRenderer->SetParameters(cg.testPostProcess.materialParms);
	glqRenderer->DrawStretchPic(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, H_SCALE, 1.0f, V_SCALE, 1.0f, cg.testPostProcess.material);
}

/*
 ==================
 CG_ClearTestPostProcess
 ==================
*/
void CG_ClearTestPostProcess (){

	// Clear the test post-process
	glqMemory->Fill(&cg.testPostProcess, 0, sizeof(testPostProcess_t));
}


/*
 ==============================================================================

 DECAL TESTING

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
void CG_TestDecal_f (){

}

/*
 ==================
 
 ==================
*/
void CG_TestSplatDecal_f (){

}